import sys
import platform
import time
import socket
import SocketServer
import select
import json

PORT = 14027

# Note using "127.0.0.1" is faster on Windows than using "localhost".
_LOCALHOST = "127.0.0.1"

_ADDRESS_FAMILY = (
    socket.AF_INET if platform.system() == "Windows" else socket.AF_UNIX)


_was_shut_down = False
def shut_down_daemon():
    global _was_shut_down
    _was_shut_down = True

def start_daemon(process_request_callback):
    class LocalTCPServer(SocketServer.TCPServer):
        allow_reuse_address = True
        address_family = _ADDRESS_FAMILY

    server = LocalTCPServer(
        (_LOCALHOST, PORT),
        lambda *args, **kwargs: _RequestHandler(
            process_request_callback, *args, **kwargs))

    while not _was_shut_down:
        r, w, e = select.select([server], [], [], 0.5)
        if r:
            server._handle_request_noblock()

class _RequestHandler(SocketServer.StreamRequestHandler):
    def __init__(self, process_request_callback, *args, **kwargs):
        self.process_request_callback = process_request_callback
        SocketServer.StreamRequestHandler.__init__(self, *args, **kwargs)

    def handle(self):
        # TODO: If we aren't using local sockets, reject connections that
        #       aren't from the local machine.  (Note that such connections
        #       could be spoofed, though.)
        request = read_from_connection(self.request)
        response = self.process_request_callback(request)
        send_over_connection(self.request, response)

def wait_for_and_open_connection():
    for timeout in (0.1, 0.1, 0.1, 0.1, 1, 2, 3):
        time.sleep(timeout)
        connection = open_connection(allow_failure=True)
        if connection is not None:
            return connection

    return None

def open_connection(allow_failure=False):
    sock = socket.socket(_ADDRESS_FAMILY, socket.SOCK_STREAM)
    try:
        sock.connect((_LOCALHOST, PORT))
        return sock
    except socket.error:
        if not allow_failure:
            raise
        return None

def send_over_connection(connection, dictionary):
    connection.send(json.dumps(dictionary) + "\0")

def read_from_connection(connection):
    result = ""
    while True:
        result += connection.recv(1024)
        if result.endswith("\0"):
            break

    assert(result.find("\0") == len(result) - 1)
    return _remove_unicode(json.loads(result[:-1]))

def _remove_unicode(obj):
    if isinstance(obj, unicode):
        return str(obj)

    if isinstance(obj, list):
        return [_remove_unicode(element) for element in obj]

    if isinstance(obj, dict):
        return dict(
            (_remove_unicode(key), _remove_unicode(value))
            for (key, value) in obj.items())

    return obj

def close_connection(connection):
    connection.close()

